import java.io.*;
import java.net.*;
import java.text.*;
import java.util.*;
/* Relevant game sections
69760 processPracticeGameInvite
70292 message parsing
85881 createGame
92529 handlePracticeGameInviteAccept
93232 handlePracticeGameInviteAcceptAck
 */
 /*
 TODO:
  Fix reason extraction (only warn works properly?)
  Log reasons better (!warnhist[ory])
  Fix AFK check (says 9/10 players, Roku's system)
  Add vouch parameter
  Add additional data tracking (join time, voucher)
  Add !game(s) (and !games N), req's getShortType in Match (and start time in Game?)
  Add makeGame to Match
  Move all dumps to DataManager (then to individual classes?)
  Add more stats (population, games in progress, streaks, !bot)
  Add better caching for live/stream
  Mention kicking from pool policy


 Long TODO:
  Change from "structures" to classes
  [In preparation for DB] Add cache for DataManager
  Implement game creation via rtmp
  Add match notices (nocturne ban etc.)
  Captain's draft


 DONE:

 */
public class LeagueBot
{
	public static final String channel = "#RIHL";
	public static final String achannel = "#RIHLAdmin";
	public static final String cmdchar = "!";
	public static final String botname = "LeagueBot";
	public static final String botpass = "asdfasdf";

	public static Random rand = new Random(System.currentTimeMillis());

	// Logs
	public static BufferedWriter log;
	public static BufferedWriter alog;

	// Message variables
	public static String name, content, action, target, text, cmd;

	// Data related variables and commands
	public static DataManager dm;

	// Cache valid nick to prevent NickServ spam
	public static HashSet<String> validnicks = new HashSet<String>();

	// Date formatter
	public static SimpleDateFormat df = new SimpleDateFormat("MMM d, h:mm a (Z)");

	// List of people in the channel
	public static HashSet<String> population = new HashSet<String>();

	// List of livestreams
	public static HashMap<String, String> streams = new HashMap<String, String>();
	public static long last_live = 0;
	public static String last_live_msg = "";

	// Spam prevention and some message budgeting
	public static SpamHandler sh = new SpamHandler();

	// Netsplit handling
	public static HashSet<String> novoice = new HashSet<String>();

	// IRC Connection
	public static IRCConnection ircconn;

	public static void main(String args[])
	{
		initData();
		ShutdownHook.attachHook();

		HashMap<String, String> channels = new HashMap<String, String>();
		channels.put(channel, "");
		channels.put(achannel, "pwnterra");

		// Reconnection loop
		while (true)
		{
			try
			{
				// Clear the caches
				validnicks = new HashSet<String>();
				population = new HashSet<String>();

				// Start the connection
				ircconn = new IRCConnection("irc.synirc.net", 6666, botname, botpass, channels);
				ircconn.start();

				// Main loop
				String line = "";
				while (true) // Loop until exception
				{
					// Way of checking for ping timeouts
					int retries;
					for (retries = 0; retries < 2400; retries++)
					{
						// Wait for a line
						line = ircconn.readLine();
						if (line != null)
							break;

						sleep(100);
					}

					// If we failed to get a line 2400 times (~4 minutes) then initiate a reconnect
					if (retries == 2400)
						break;

					// Need to reset the command so it's not blocked
					cmd = "";

					// Log everything!
					log.write((System.currentTimeMillis() / 1000) + " " + line + "\n");
					log.flush();

					// Get voice if needed (this happened once when services were down)
					if (line.contains("404 " + botname + " " + channel + " :You need voice (+v) (" + channel + ")"))
					{
						ircconn.write("PART " + channel + " :Trying to get OP");
						ircconn.write("JOIN " + channel);

						// Wait 10s
						try { Thread.sleep(10000); } catch (Exception e) { }
						continue;
					}

					// Set up the population
					//:nightmare.uk.eu.synirc.net 333 LeagueBot #RIHL Roku 1299915343
					//:nightmare.uk.eu.synirc.net 353 LeagueBot = #RIHL :+Meikat GuitarGuy182 %GuitarGuy @_Jabe
					if (line.contains("353 LeagueBot = #RIHL :"))
					{
						population.add("Roku"); // Roku (owner) doesn't show up in the 353 message
						String[] peeps = line.substring(line.indexOf(":", 1) + 1).replaceAll("[+&@%]", "").split(" ");
						for (String peep : peeps)
							population.add(peep);
					}

					// Only handle user actions at this point
					if (!line.contains("!"))
						continue;

					// Message parsing
					name = line.substring(1, line.indexOf("!")); // Message sender
					content = line.substring(line.indexOf(" ") + 1);
					action = content.substring(0, content.indexOf(" "));
					content = content.substring(content.indexOf(" ") + 1); // Everything except the action

					// Ignore actions in #RIHLAdmin except PRIVMSG
					if (content.contains(achannel) && !action.equals("PRIVMSG"))
						continue;

					// Handle JOIN
					if (action.equals("JOIN"))
					{
						// Bot handles voice
						// Don't revoice people that got netsplit
						if (novoice.contains(name))
							novoice.remove(name);
						// Voice registered people that aren't mods (except for "hidden" _Jabe)
						else if (dm.isRegistered(name) && (!dm.isMod(name) || name.equals("_Jabe")))
							sendMsg("ChanServ", "VOICE " + channel + " " + name);
					}

					// Handle KICK/NICK/PART/QUIT
					if (action.equals("KICK") || action.equals("NICK") || action.equals("PART") || action.equals("QUIT"))
					{
						// For KICK, the target is in the content
						if (action.equals("KICK"))
							target = content.substring(content.indexOf(" ") + 1, content.lastIndexOf(" "));
						else
							target = name;

						// Need to add new nick to the population, and potentially voice as well
						if (action.equals("NICK"))
						{
							name = content.substring(content.lastIndexOf(":") + 1);
							population.add(name);
							if (dm.isRegistered(name) && !dm.isMod(name))
								sendMsg("ChanServ", "VOICE " + channel + " " + name);
						}

						population.remove(target);
						validnicks.remove(target);

						// Remove them from the pool if applicable
						if (dm.isInPool(target))
						{
							int type = dm.removePlayer(target);
							dm.dumpLobby();
							sendMsg(channel, dm.getSumm(target) + " has left the pool for " + Match.getType(type));
						}

						// In the case of netsplits, add them to a list so there isn't any voice spam
						//:Ryrk!~43aaf824@synIRC-5003FED7.hsd1.ca.comcast.net QUIT :naamio.fi.eu.synirc.net thoth.hub.eu.synirc.net
						if (action.equals("KICK") && content.contains("synirc.net"))
							novoice.add(name);
					}

					// Raw data listing
					//:Ququroon!~Ququroon@synIRC-D4705AAA.hsd1.wa.comcast.net PART #RIHL
					//:Roku!Roku@synIRC-490951BE.nat.resnet.nau.edu QUIT :Ping timeout
					//:_Jabe|test!~gabe@synIRC-D44421EA.riws.ualberta.ca NICK :_Jabe
					//:_Jabe!~gabe@synIRC-D44421EA.riws.ualberta.ca KICK #RIHL Keipi :_Jabe
					//:KillinUrDoodz!~KillinUrD@synIRC-5AB37110.dyn.optonline.net PRIVMSG #RIHL :!help

					// We only care about messages at this point
					if (!action.equals("PRIVMSG"))
						continue;

					// Mark as not AFK if necessary
					dm.markNotAFK(name);

					// More Message parsing
					target = content.substring(0, content.indexOf(" ")); // Message target (channel/user)
					text = content.substring(content.indexOf(":") + 1);  // Message content
					boolean handled = false;

					// SUPERADMIN
					if ((name.equals("_Jabe") || name.equals("Roku")) && validNick(name))
						handled = handleSuperAdmin();
					if (handled) continue;

					// Check for messages to bot or admin channel
					if (target.equalsIgnoreCase(botname) || target.equals(achannel))
					{
						// ADMIN
						if (dm.isAdmin(name) && validNick(name))
							handled = handleAdmin();
						if (handled) continue;

						// MODERATOR
						if (dm.isMod(name) && validNick(name))
							handled = handleMod();
						if (handled) continue;
					}

					// COMMANDS

					// Handle "missing" exclamation mark in PMs
					cmd = text; // consists of the command without the exclamation mark
					if (!cmd.startsWith(cmdchar))
					{
						if (!target.equalsIgnoreCase(botname))
							continue;
						else
							text = cmdchar + text;
					}
					else
					{
						cmd = cmd.substring(1);
					}

					// Remove the command content if any
					int idx = cmd.indexOf(" ");
					if (idx != -1)
						cmd = cmd.substring(0, idx);
					cmd = cmd.toLowerCase();

					// Block messages if needed (!top spam, etc.)
					if (!sh.allowCmd(cmd))
						continue;

					// Test command
					if (cmd.equals("test") && name.equals("_Jabe"))
					{
						// Put whatever here
					}

					// General commands
					if (cmd.equals("help"))
						handleHelp();
					//else if (cmd.equals("startcp"))
					//	handleStartCaptain();
					else if (cmd.equals("join"))
						handleJoin();
					else if (cmd.equals("leave"))
						handleLeave();
					else if (cmd.equals("lobby") || cmd.startsWith("pool"))
						handleLobby();
					else if (cmd.equals("cancel"))
						handleCancel();
					else if (cmd.equals("win") || cmd.equals("won"))
						handleWin();
					else if (cmd.equals("loss") || cmd.equals("lose"))
						handleLoss();
					else if (cmd.equals("noshow"))
						handleNoShow();
					else if (cmd.equals("leaver"))
						handleLeaver();
					else if (cmd.equals("summ"))
						handleSumm();
					else if (cmd.equals("nick"))
						handleNick();
					else if (cmd.equals("top"))
						handleTop();
					else if (cmd.equals("me"))
						handleMe();
					else if (cmd.equals("stats"))
						handleStats();
					else if (cmd.equals("gamestats"))
						handleGameStats();
					else if (cmd.equals("gamehistory"))
						handleGameHistory();
					else if (cmd.equals("vent"))
						handleVent();
					else if (cmd.equals("stream"))
						handleStream();
					else if (cmd.equals("live"))
						handleLive();
					else if (cmd.equals("admin"))
						handleAdminCall();
					else if (cmd.equals("report"))
						handleReport();
				}
			}
			catch (Exception e) { e.printStackTrace(); }

			// Close out the old thread
			ircconn.die();
		}
	}

	private static void sleep(int ms)
	{
		try { Thread.sleep(ms); } catch (Exception e) { }
	}

	/*******************\
	  Loading Functions
	\*******************/
	public static void initData()
	{
		dm = DataManager.getInstance();

		// Create the files if necessary
		File f;

		try
		{
			f = new File("log.txt");
			if (!f.exists())
				f.createNewFile();

			f = new File("adminlog.txt");
			if (!f.exists())
				f.createNewFile();

			f = new File("streams.txt");
			if (!f.exists())
				f.createNewFile();
		}
		catch (IOException e)
		{
			System.out.println("Failed to create initial files");
			e.printStackTrace();
			System.exit(0);
		}

		try
		{
			// Open the logs
			log = new BufferedWriter(new FileWriter("log.txt", true));
			alog = new BufferedWriter(new FileWriter("adminlog.txt", true));

			BufferedReader in;
			String line;

			// Read in stream data
			in = new BufferedReader(new FileReader("streams.txt"));
			while ((line = in.readLine()) != null)
			{
				if (line.length() == 0)
					continue;

				String[] data = line.split(" ");
				streams.put(data[0].toLowerCase(), data[1]);
			}
			in.close();
		}
		catch (Exception e)
		{
			e.printStackTrace();
			System.exit(0);
		}
	}

	public static void cleanup()
	{
		try { log.close(); } catch (Exception e) { }
		try { alog.close(); } catch (Exception e) { }

		ircconn.die();
	}

	public static BufferedReader readURL(String url)
	{
		try
		{
			return new BufferedReader(new InputStreamReader(new URL(url).openStream()));
		}
		catch (Exception e) { e.printStackTrace(); }

		return null;
	}
	/***********************\
	  End Loading Functions
	\***********************/



	/*************\
	  IRC Related
	\*************/
	public static synchronized void sendMsg(String target, String msg)
	{
		if (msg == null)
			return;

		// Delay messages if there are too many targets
		String[] targets = target.split(",");
		String final_target = null;
		for (String targ : targets)
		{
			long delay = sh.getDelay(targ);
			if (delay > 0)
			{
				QueuedMessage qm = new QueuedMessage(targ, msg, delay);
				qm.start();
			}
			else
			{
				if (final_target == null)
					final_target = "";
				else
					final_target += ",";

				final_target += targ;
			}
		}

		if (ircconn == null)
		{
			QueuedMessage qm = new QueuedMessage(final_target, msg, 1000);
			qm.start();
		}
		else
		{
			ircconn.sendMsg(final_target, msg);
		}
	}

	public static boolean validNick(String name)
	{
		if (validnicks.contains(name))
			return true;

		sendMsg("NickServ", "STATUS " + name);

		String line = "";
		// Wait until we get the right line (susceptible to ping timeouts)
		while (!line.startsWith(":NickServ!services@services.synirc.net NOTICE " + botname + " :STATUS " + name))
		{
			while ((line = ircconn.readLineBuffered()) == null)
				sleep(100);
		}

		String data = line.substring(line.indexOf("NOTICE") + 7);
		String text = data.substring(data.indexOf(":") + 1);

		if (text.equals("STATUS " + name + " 3"))
		{
			validnicks.add(name);
			return true;
		}
		else if (text.equals("STATUS " + name + " 1"))
		{
			sendMsg(name, "You are not identified as your nick with NickServ.  Type '/msg NickServ identify [password]' to identify yourself");
			return false;
		}
		else
		{
			// Not handling STATUS = 2, hasn't come up yet
			sendMsg(name, "You must first register with NickServ.  Type '/msg NickServ help register' to learn how");
			return false;
		}
	}

	public static String getStreamData(String url)
	{
		// Request contents:
		//liveViewers=661&liveStatus=true&liveVerified=1

		String id = url.substring(url.lastIndexOf("/") + 1);
		String ret = " & & ";

		try
		{
			BufferedReader temp = readURL("http://static.ec.own3d.tv/live_tmp/" + id + ".txt?" + System.currentTimeMillis());
			ret = temp.readLine();

			// Not sure what to do if this happens yet
			if (ret == null)
			{
				System.err.println("Null stream data - http://static.ec.own3d.tv/live_tmp/" + id + ".txt?" + System.currentTimeMillis());
				ret = " & & ";
			}

			temp.close();
		}
		catch (Exception e) { e.printStackTrace(); }

		return ret;
	}

	public static void logAdmin(String text)
	{
		try
		{
			alog.write((System.currentTimeMillis() / 1000) + ": " + text + "\n");
			alog.flush();
		}
		catch (Exception e)
		{
			System.err.println("Failed to write to log!");
			e.printStackTrace();
			System.exit(0);
		}
	}
	/*****************\
	  End IRC Related
	\*****************/



	/************************\
	  Basic Command Handlers
	\************************/
	public static void handleHelp()
	{
		String cmd = text.substring(5).trim().toLowerCase();
		if (cmd.startsWith(cmdchar))
			cmd = cmd.substring(1);

		String msg = null;

		if (!dm.isRegistered(name))
		{
			if (cmd.equals("summ"))
				msg = cmdchar + "summ [nick] -- Returns the summoner name of the given IRC nick";
			else if (cmd.equals("nick"))
				msg = cmdchar + "nick [summoner] -- Returns the IRC nick of the given summoner name";
			else if (cmd.equals("top"))
				msg = cmdchar + "top [n] -- Returns the stats for the top N summoners in the league. N is 5 by default and at most 10";
			else if (cmd.equals("stats"))
				msg = cmdchar + "stats [summoner] -- Returns the rating, wins, and losses for the given summoner";
			else if (cmd.equals("gamestats"))
				msg = cmdchar + "gamestats [id] -- Returns the result and rating changes for the given game ID";
			else if (cmd.equals("gamehistory"))
				msg = cmdchar + "gamehistory [summoner] -- Returns the past 5 game ID's for the given summoner";
			else
				msg = "Available commands: "
					+ cmdchar + "summ " + cmdchar + "nick " + cmdchar + "top "
					+ cmdchar + "stats " + cmdchar + "gamestats " + cmdchar + "gamehistory"
					+ "; type '" + cmdchar + "help [command]' for more information on a specific command.  See this post for information on how to register: http://runeterra.com/forums/index.php?topic=2681.0";
		}
		else
		{
			if (cmd.equals("join"))
				msg = cmdchar + "join [type] -- Joins a match pool.  The default pool is 5v5.  Valid types are 5v5 (or rift) and 3v3 (or tt)";
			else if (cmd.equals("leave"))
				msg = cmdchar + "leave -- Leaves the pool you have joined";
			else if (cmd.equals("lobby"))
				msg = cmdchar + "lobby -- Displays all pools and players in each";
			else if (cmd.equals("cancel"))
				msg = cmdchar + "cancel -- Votes to cancel the game.  Requires team size + 1 votes.  This should be used when the players in the game agree to cancel it for Internet difficulties, etc.  It should NOT be used because the teams are 'imbalanced'";
			else if (cmd.equals("win"))
				msg = cmdchar + "win -- Reports a win for your team.  After at least half from each team have reported, the game result is recorded";
			else if (cmd.equals("loss"))
				msg = cmdchar + "loss -- Reports a loss for your team.  After at least half from each team have reported, the game result is recorded";
			else if (cmd.equals("noshow"))
				msg = cmdchar + "noshow [summoner] -- Reports a summoner for not showing up to the game.  Requires team size + 1 votes.  If successful, aborts the game and the noshow is warned";
			else if (cmd.equals("leaver"))
				msg = cmdchar + "leaver [summoner] -- Reports a summoner for leaving a game.  The game result must still be reported.  Requires team size + 1 votes.  If successful, the leaver is warned";
			else if (cmd.startsWith("summ"))
				msg = cmdchar + "summ [nick] -- Returns the summoner name of the given IRC nick.  Defaults to yourself";
			else if (cmd.equals("nick"))
				msg = cmdchar + "nick [summoner] -- Returns the IRC nick of the given summoner name";
			else if (cmd.equals("top"))
				msg = cmdchar + "top [n] -- Returns the stats for the top N summoners in the league. N is 5 by default and at most 10";
			else if (cmd.equals("me"))
				msg = cmdchar + "me -- Returns your stats and warn level in a PM";
			else if (cmd.equals("stats"))
				msg = cmdchar + "stats [summoner] -- Returns the rating, wins, and losses for the given summoner.  Default summoner is yourself";
			else if (cmd.equals("gamestats"))
				msg = cmdchar + "gamestats [id] -- Returns the result and rating changes for the given game id";
			else if (cmd.equals("gamehistory"))
				msg = cmdchar + "gamehistory [summoner] -- Returns the past 5 game ID's for the given summoner";
			else if (cmd.equals("vent"))
				msg = cmdchar + "vent -- Returns the vent information.  Vent info: zinc.typefrag.com --- Port: 21483 --- Password: staypositive";
			else if (cmd.equals("stream"))
				msg = cmdchar + "stream [name] -- Displays the stream link and live status for the person if they have one.  Default is your own stream.  PM Jabe with your links (own3d only)";
			else if (cmd.equals("live"))
				msg = cmdchar + "live -- PM's a list of streams that are online";
			else if (cmd.equals("admin"))
				msg = cmdchar + "admin -- Requests attention from an admin/moderator.  Do not abuse!";
			else if (cmd.equals("report"))
				msg = cmdchar + "report [name] [reason] -- Reports a player for the reason specified.  Only accepted via PM.  Do not abuse!";
			else
				msg = "Available commands: "
					+ cmdchar + "join " + cmdchar + "leave " + cmdchar + "lobby -- "
					+ cmdchar + "win/loss " + cmdchar + "cancel " + cmdchar + "noshow " + cmdchar + "leaver -- "
					+ cmdchar + "summ " + cmdchar + "nick " + cmdchar + "top " + cmdchar + "me " + cmdchar + "stats " + cmdchar + "gamestats " + cmdchar + "gamehistory -- "
					+ cmdchar + "vent " + cmdchar + "live " + cmdchar + "stream -- "
					+ cmdchar + "admin " + cmdchar + "report"
					+ "; type '" + cmdchar + "help [command]' for more information on a specific command";
		}

		if (dm.isMod(name))
		{
			if (cmd.equals("mod"))
				msg = "Available moderator commands: "
					+ cmdchar + "register " + cmdchar + "ban " + cmdchar + "warn -- "
					+ cmdchar + "contested " + cmdchar + "setwin -- "
					+ cmdchar + "reports " + cmdchar + "report " + cmdchar + "resolve"
					+ "; moderator commands are only accepted via PM; type '" + cmdchar + "help [command]' for more information on a specific command";
			else if (cmd.equals("ban"))
				msg = cmdchar + "ban [summoner] [reason] -- Bans the given summoner from the league for the specified reason.  Does not work on moderators and above";
			else if (cmd.equals("register"))
				msg = cmdchar + "register [nick] [summoner] -- Registers the IRC nick as the summoner.  Can be used to change a nick's summoner name or a summoner's nick as well.  Spaces are removed from the summoner name";
			else if (cmd.equals("warn"))
				msg = cmdchar + "warn [summoner] [amount] [reason] -- Warns the summoner for the given reason.  To remove warn, use a negative amount";
			else if (cmd.equals("contested"))
				msg = cmdchar + "contested -- Returns a list of contested game ID's";
			else if (cmd.equals("setwin"))
				msg = cmdchar + "setwin [id] [result] [reason] -- Sets the given game ID's result.  Valid results are blue, purple, and void. Note: Only changes the result and ratings; does not handle warn from incorrect reports etc.";
			else if (cmd.equals("reports"))
				msg = cmdchar + "reports -- Returns a list of unresolved reports";
			else if (cmd.equals("report"))
				msg = cmdchar + "report [id] -- Returns the specified report information";
			else if (cmd.equals("resolve"))
				msg = cmdchar + "resolve [id] [notes] -- Resolves the specified report and leaves a note";
		}

		if (dm.isAdmin(name))
		{
			if (cmd.equals("admin"))
				msg = "Available admin commands: "
					+ cmdchar + "promote " + cmdchar + "demote"
					+ "; admin commands are only accepted via PM; type '" + cmdchar + "help [command]' for more information on a specific command";
			else if (cmd.equals("promote"))
				msg = cmdchar + "promote [nick] -- Promotes an IRC nick to moderator";
			else if (cmd.equals("demote"))
				msg = cmdchar + "demote [nick] [reason] -- Demotes an IRC nick from moderator to member for the given reason";
		}

		sendMsg(name, msg);
	}

	public static void handleStartCaptain()
	{
	}

	public static void handleJoin()
	{
		if (!validNick(name) || !dm.isRegistered(name))
			return;

		if (dm.isInGame(name))
		{
			sendMsg(name, "You have a game pending with an unrecorded result and therefore cannot join anymore games.  Make sure you have done !win/!lose.  If the game is contested, please wait for an admin to resolve it");
		}
		else if (!dm.isInPool(name))
		{
			String type;
			int idx = text.indexOf(" ");
			if (idx == -1)
				type = "";
			else
				type = text.substring(idx).trim().toLowerCase();

			int itype = 0;
			if (type.equals("5v5") || type.equals("rift"))
				itype = Match.FIVE_IH;
			else if (type.equals("3v3") || type.equals("tt"))
				itype = Match.THREE_IH;
			//else if (type.equals("1v1"))
			//	itype = Match.ONE_VS_ONE;
			else
				itype = Match.FIVE_IH;

			dm.addPlayer(name, itype);
		}
	}

	public static void handleLeave()
	{
		int type = dm.removePlayer(name);
		dm.dumpLobby();

		if (type != -1)
			sendMsg(channel, dm.getSumm(name) + " has left the pool for " + Match.getType(type));
	}

	public static void handleLobby()
	{
		if (target.equals(botname))
			sendMsg(name, dm.listPools());
		else
			sendMsg(channel, dm.listPools());
	}

	public static void handleCancel()
	{
		dm.handleCancel(name);
	}

	public static void handleWin()
	{
		dm.handleReport(name, true);
	}

	public static void handleLoss()
	{
		dm.handleReport(name, false);
	}

	public static void handleNoShow()
	{
		dm.handleNoShow(name, text.substring(7).trim());
	}

	public static void handleLeaver()
	{
		dm.handleLeaver(name, text.substring(7).trim());
	}

	public static void handleSumm()
	{
		String nick;
		if (!text.contains(" "))
			nick = name;
		else
			nick = text.substring(text.indexOf(" ")).trim();

		String summ = dm.getSumm(nick);
		if (summ == null)
			return;

		if (target.equalsIgnoreCase(botname))
			target = name;

		sendMsg(target, nick + "'s summoner name is " + summ);
	}

	public static void handleNick()
	{
		if (!text.contains(" "))
			return;

		String summ = text.substring(text.indexOf(" ")).trim();
		summ = summ.replaceAll(" ", "");

		String nick = dm.getNick(summ);
		if (nick == null)
			return;

		if (target.equalsIgnoreCase(botname))
			target = name;

		sendMsg(target, summ + "'s IRC nick is " + nick);
	}

	public static void handleTop()
	{
		int idx = text.indexOf(" ");
		int top = 5;
		if (idx != -1)
			try { top = Integer.parseInt(text.substring(idx + 1)); } catch (Exception e) { }

		if (target.equalsIgnoreCase(botname))
			target = name;

		sendMsg(target, dm.getTop(top));
	}

	public static void handleMe()
	{
		if (!dm.isRegistered(name))
			return;

		sendMsg(name, dm.getStats(name, true));
	}

	public static void handleStats()
	{
		int idx = text.indexOf(" ");
		String summ;

		if (idx == -1)
		{
			summ = dm.getSumm(name);
		}
		else
		{
			summ = text.substring(idx).trim();
			if (summ.length() == 0)
				summ = dm.getSumm(name);
		}

		if (summ == null)
			return;

		String nick = dm.getNick(summ);
		if (nick == null)
			nick = summ;

		if (!dm.isRegistered(nick))
			return;

		if (target.equalsIgnoreCase(botname))
			target = name;

		sendMsg(target, dm.getStats(nick));
	}

	public static void handleGameStats()
	{
		int idx = text.indexOf(" ");
		if (idx == -1)
			return;

		String ids = text.substring(idx).trim();
		ids = ids.replaceAll("[^0-9]", "");

		int id = -1;
		try { id = Integer.parseInt(ids); } catch (Exception e) { }
		if (id == -1)
			return;

		String hist = dm.getHistory(id);
		if (hist == null)
			return;

		long timestamp = Long.parseLong(hist.substring(0, hist.indexOf(" ")));
		hist = hist.substring(hist.indexOf(" ") + 1);

		sendMsg(name, df.format(new Date(timestamp)) + " -- " + hist);
	}

	public static void handleGameHistory()
	{
		int idx = text.indexOf(" ");
		String summ;

		if (idx == -1)
			summ = dm.getSumm(name);
		else
		{
			summ = text.substring(idx).trim();
			if (summ.length() == 0)
				summ = dm.getSumm(name);
		}

		String nick = dm.getNick(summ);
		if (nick == null)
			nick = summ;

		if (!dm.isRegistered(nick))
			return;

		summ = dm.getSumm(nick);

		sendMsg(name, summ + "'s past game IDs: " + dm.getHistory(nick));
	}

	public static void handleVent()
	{
		if (target.equalsIgnoreCase(botname))
			target = name;

		sendMsg(target, "Vent info: zinc.typefrag.com --- Port: 21483 --- Password: staypositive");
	}

	public static void handleStream()
	{
		String summ;
		int idx = text.indexOf(" ");

		if (idx == -1)
		{
			summ = dm.getSumm(name);
		}
		else
		{
			summ = text.substring(idx).trim();

			if (summ.length() == 0)
				summ = dm.getSumm(name);
		}

		String nick = dm.getNick(summ);
		if (nick == null)
			nick = summ;
		nick = nick.toLowerCase();

		if (!streams.containsKey(nick))
			return;

		summ = dm.getSumm(nick);

		if (target.equalsIgnoreCase(botname))
			target = name;

		String temp = getStreamData(streams.get(nick));
		String[] sdata = temp.split("&");

		String info;
		if (sdata[1].contains("true"))
		{
			info = "Online, ";
			info += Integer.parseInt(sdata[0].substring(sdata[0].indexOf("=") + 1));
			info += " viewers";
		}
		else
		{
			info = "Offline";
		}

		sendMsg(target, summ + "'s livestream (" + info + "): " + streams.get(nick));
	}

	public static void handleLive()
	{
		target = name;

		String msg = "";
		if (last_live + 600000 < System.currentTimeMillis())
		{
			for (String nick : streams.keySet())
			{
				// Only check streams of players in a match
				if (!dm.isInGame(nick))
					continue;

				String temp = getStreamData(streams.get(nick));
				String[] sdata = temp.split("&");
				if (sdata[1].contains("true"))
				{
					msg += dm.getSumm(nick) + "'s livestream (" + Integer.parseInt(sdata[0].substring(sdata[0].indexOf("=") + 1)) + " viewers): " + streams.get(nick) + "\n";
				}
			}

			last_live = System.currentTimeMillis();
		}
		else
		{
			msg = last_live_msg;
		}

		if (msg.length() == 0)
			msg = "No RIHL games are being streamed right now";

		last_live_msg = msg;

		sendMsg(target, msg);
	}

	public static void handleReport()
	{
		if (!target.equals(botname))
			return;

		text = text.replaceAll("\\|", ""); // Strip pipes since that's the delimiter

		int idx1 = text.indexOf(" ");
		int idx2 = text.indexOf(" ", idx1 + 1);
		if (idx1 == -1 || idx2 == -1)
			return;

		String player = text.substring(idx1 + 1, idx2);
		String reason = text.substring(idx2 + 1);

		dm.addReport(name, player, reason);

		sendMsg(name, "Report logged");
		sendMsg(achannel, name + " is reporting " + player + " for the following reason: " + reason);
	}

	public static void handleAdminCall()
	{
		sendMsg(achannel, name + " is requesting assistance");
	}
	/****************************\
	  End Basic Command Handlers
	\****************************/



	/************************\
	  Admin Command Handlers
	\************************/
	public static boolean handleSuperAdmin()
		throws java.io.IOException
	{
		boolean valid = false;

		// Remove OP status so I don't get PM'ed
		if (text.startsWith(cmdchar + "hide"))
		{
			sendMsg("ChanServ", "DEOP " + channel + " _Jabe");
			sendMsg("ChanServ", "VOICE " + channel + " _Jabe");
		}
		// Get OP status back
		else if (text.startsWith(cmdchar + "show"))
		{
			sendMsg("ChanServ", "OP " + channel + " _Jabe");
		}
		// Make someone "say" something (for testing purposes)
		else if (text.startsWith(cmdchar + "name"))
		{
			logAdmin("SUPERADMIN (" + name + ") - " + text);

			text = text.substring(text.indexOf(" ")).trim();
			name = text.substring(0, text.indexOf(" "));
			text = text.substring(text.indexOf(" ")).trim();

			return false;
		}
		// Promote someone (up one level, moderator -> admin)
		else if (text.startsWith(cmdchar + "promote"))
		{
			int idx = text.indexOf(" ");
			if (idx == -1)
				return false;

			String nick = text.substring(idx).trim();
			if (!dm.isRegistered(nick)) // Guess the 'nick' is a 'summ' then
				nick = dm.getNick(nick);

			if (dm.isMod(nick) && !dm.isAdmin(nick))
			{
				dm.getPlayer(nick).admin = true;
				dm.dumpPlayers();

				sendMsg(name, nick + " promoted to Admin");
				sendMsg(nick, "You have been promoted to Admin");
				sendMsg("ChanServ", "AOP " + channel + " ADD " + nick);
				sendMsg("ChanServ", "OP " + channel + " " + nick);

				valid = true;
			}
		}
		// Demote someone (down one level, admin -> moderator)
		else if (text.startsWith(cmdchar + "demote"))
		{
			int idx = text.indexOf(" ");
			if (idx == -1)
				return false;

			String nick = text.substring(idx).trim();

			// Check if there was a reason
			idx = nick.indexOf(" ");
			if (idx == -1)
			{
				sendMsg(name, "Include a reason");
				return true;
			}
			nick = nick.substring(0, idx);

			// If the nick isn't valid...
			if (!dm.isRegistered(nick))
				nick = dm.getNick(nick); // ... guess the 'nick' is a 'summ' then

			if (dm.isAdmin(nick))
			{
				dm.getPlayer(nick).admin = false;
				dm.dumpPlayers();

				sendMsg(name, nick + " demoted to Moderator");
				sendMsg(nick, "You have been demoted to Moderator");
				sendMsg("ChanServ", "DEOP " + channel + " " + nick);
				sendMsg("ChanServ", "HOP " + channel + " ADD " + nick);
				sendMsg("ChanServ", "HALFOP " + channel + " " + nick);

				valid = true;
			}
		}
		// Force the bot to quit (the only true exit)
		else if (text.startsWith(cmdchar + "quit"))
		{
			String msg = "Restarting";
			if (text.length() > 6)
				msg = text.substring(6);

			ircconn.write("PART " + channel + " :" + msg);
			ircconn.write("PART " + achannel + " :" + msg);
			ircconn.write("QUIT");
			ircconn.die();

			cleanup();

			System.exit(0);
		}

		// Log commands if necessary
		if (valid)
		{
			logAdmin("SUPERADMIN (" + name + ") - " + text);
			sendMsg(achannel, "SUPERADMIN (" + name + ") - " + text);
		}

		return valid;
	}

	public static boolean handleAdmin()
	{
		boolean valid = false;

		// Promote someone (up one level, member -> moderator)
		if (text.startsWith(cmdchar + "promote"))
		{
			int idx = text.indexOf(" ");
			if (idx == -1)
				return false;

			String nick = text.substring(idx).trim();
			if (!dm.isRegistered(nick)) // Guess the 'nick' is a 'summ' then
				nick = dm.getNick(nick);

			if (dm.isRegistered(nick) && !dm.isMod(nick))
			{
				dm.getPlayer(nick).moderator = true;
				dm.dumpPlayers();

				if (target.equalsIgnoreCase(botname))
					target = name;

				sendMsg(target, nick + " promoted to Moderator");
				sendMsg(nick, "You have been promoted to Moderator");
				sendMsg("ChanServ", "HOP " + channel + " ADD " + nick);
				sendMsg("ChanServ", "HALFOP " + channel + " " + nick);

				valid = true;
			}
		}
		// Demote someone (down one level, moderator -> member)
		else if (text.startsWith(cmdchar + "demote"))
		{
			int idx = text.indexOf(" ");
			if (idx == -1)
				return false;

			String nick = text.substring(idx).trim();

			// Check if there was a reason
			idx = nick.indexOf(" ");
			if (idx == -1)
			{
				sendMsg(name, "Include a reason");
				return true;
			}
			nick = nick.substring(0, idx);

			// If the nick isn't valid...
			if (!dm.isRegistered(nick))
				nick = dm.getNick(nick); // ... guess the 'nick' is a 'summ' then

			if (dm.isMod(nick) && !dm.isAdmin(nick))
			{
				dm.getPlayer(nick).moderator = false;
				dm.dumpPlayers();

				if (target.equalsIgnoreCase(botname))
					target = name;

				sendMsg(target, nick + " demoted to Member");
				sendMsg(nick, "You have been demoted to Member");
				sendMsg("ChanServ", "DEHALFOP " + channel + " " + nick);
				//sendMsg("ChanServ", "VOP " + channel + " ADD " + nick);
				sendMsg("ChanServ", "VOICE " + channel + " " + nick);

				valid = true;
			}
		}

		// Log commands if necessary
		if (valid)
		{
			logAdmin("ADMIN (" + name + ") - " + text);
			sendMsg(achannel, "ADMIN (" + name + ") - " + text);
		}

		return valid;
	}

	public static boolean handleMod()
	{
		boolean valid = false;

		// Register - Link a summoner name to an IRC nick
		if (text.startsWith(cmdchar + "register"))
		{
			// Quick check for minimum amount of args
			String[] temp = text.split(" ");
			if (temp.length < 3)
				return false;

			int idx1 = text.indexOf(" ");
			int idx2 = text.indexOf(" ", idx1 + 1);
			String nick = text.substring(idx1 + 1, idx2);
			String summ = text.substring(idx2 + 1);
			summ = summ.replaceAll(" ", "");

			if (target.equalsIgnoreCase(botname))
				target = name;

			if (!validNick(nick))
			{
				sendMsg(target, nick + " must register their IRC nick with NickServ (and be logged in) before they can be registered in the league");
				return true;
			}

			dm.registerPlayer(nick, summ);

			dm.dumpPlayers();

			//sendMsg("ChanServ", "VOP " + channel + " ADD " + nick);
			sendMsg("ChanServ", "VOICE " + channel + " " + nick);
			sendMsg(target, "Registered " + nick + " as " + summ);
			sendMsg(nick, "You have been registered in RIHL as " + summ + ".  Type !help in #RIHL or here to get a list of commands.  http://runeterra.com/forums/index.php?topic=2686.0 contains a sample game starting and an overview of commands");

			valid = true;
		}
		// Ban someone (only Member level)
		else if (text.startsWith(cmdchar + "ban"))
		{
			int idx = text.indexOf(" ");
			if (idx == -1)
				return false;

			String nick = text.substring(idx).trim();

			// Check if there was a reason
			String reason;
			idx = nick.indexOf(" ");
			if (idx == -1)
			{
				sendMsg(name, "Include a reason of at least 10 characters");
				return true;
			}
			reason = nick.substring(idx).trim();
			nick = nick.substring(0, idx);

			// If the nick isn't valid...
			if (!dm.isRegistered(nick))
				nick = dm.getNick(nick); // ... guess the 'nick' is a 'summ' then

			// Force reasons to be 10 characters or more
			if (reason.length() < 10)
			{
				sendMsg(target, "Include a reason of at least 10 characters");
				return true;
			}

			// Check for a valid summoner
			if (!dm.isRegistered(nick) || dm.isMod(nick))
				return false;

			dm.getPlayer(nick).registered = false;
			dm.dumpPlayers();

			//sendMsg("ChanServ", "VOP " + channel + " DEL " + nick);
			sendMsg("ChanServ", "DEVOICE " + channel + " " + nick);
			sendMsg(target, dm.getSumm(nick) + " was banned");
			sendMsg(nick, "You were banned for this reason: " + reason);

			valid = true;
		}
		// Warns a player
		else if (text.startsWith(cmdchar + "warn"))
		{
			int idx = text.indexOf(" ");
			if (idx == -1)
				return false;

			String nick = text.substring(idx).trim();

			// Check if there was a reason
			String reason;
			idx = nick.indexOf(" ");
			if (idx == -1)
				return false;

			reason = nick.substring(idx).trim(); // Is actually warn amount + reason
			nick = nick.substring(0, idx);

			// If the nick isn't valid...
			if (!dm.isRegistered(nick))
				nick = dm.getNick(nick); // ... guess the 'nick' is a 'summ' then

			// Check for a valid summoner
			if (!dm.isRegistered(nick))
				return false;

			// Extract warn amount
			idx = reason.indexOf(" ");
			if (idx == -1)
				return false;

			int warn = 0;
			try { warn = Integer.parseInt(reason.substring(0, idx)); } catch (Exception e) { }
			reason = reason.substring(idx).trim();

			// Force reasons to be 10 characters or more
			if (reason.length() < 10)
			{
				sendMsg(target, "Include a reason of at least 10 characters");
				return true;
			}

			dm.warn(nick, warn);

			sendMsg(target, dm.getSumm(nick) + " received " + warn + " warn for the following reason: " + reason);
			sendMsg(nick, "You received " + warn + " warn for the following reason: " + reason);

			valid = true;
		}
		// Display full stats (including warn)
		else if (text.startsWith(cmdchar + "stats"))
		{
			String summ = text.substring(6).trim();
			if (summ.length() == 0)
				summ = dm.getSumm(name);

			if (summ == null)
				return false;

			String nick = dm.getNick(summ);
			if (nick == null)
				nick = summ;

			if (!dm.isRegistered(nick))
				return false;

			if (target.equalsIgnoreCase(botname))
				target = name;

			sendMsg(target, dm.getStats(nick, true));

			valid = true;
		}
		// Lists unresolved reports
		else if (text.startsWith(cmdchar + "reports"))
		{
			sendMsg(name, dm.listReports());

			valid = true;
		}
		// Lists a specific report information
		else if (text.startsWith(cmdchar + "report"))
		{
			int idx = text.indexOf(" ");
			if (idx == -1)
				return false;

			int id = -1;
			try { id = Integer.parseInt(text.substring(idx).trim()); } catch (Exception e) { }

			if (id == -1)
				return false;

			sendMsg(name, dm.getReport(id));

			valid = true;
		}
		// Resolves a report
		else if (text.startsWith(cmdchar + "resolve"))
		{
			int idx1 = text.indexOf(" ");
			int idx2 = text.indexOf(" ", idx1 + 1);
			if (idx1 == -1 || idx2 == -1)
				return false;

			int id = -1;
			try { id = Integer.parseInt(text.substring(idx1 + 1, idx2)); } catch (Exception e) { }

			if (id == -1)
				return false;

			String notes = text.substring(idx2).trim();
			boolean success = dm.resolveReport(name, id, notes);

			if (success)
			{
				sendMsg(achannel, name + " resolved report #" + id + " - " + notes);
				valid = true;
			}
			else
			{
				sendMsg(name, "Invalid report id");
			}
		}
		// Lists contested games
		else if (text.startsWith(cmdchar + "contested"))
		{
			dm.sendContested(name);

			valid = true;
		}
		// Modifies a game result
		else if (text.startsWith(cmdchar + "setwin"))
		{
			String[] cmd = text.split(" ");

			if (cmd.length < 3)
				return false;

			cmd[2] = cmd[2].toLowerCase();
			cmd[1] = cmd[1].replaceAll("[^0-9]", "");

			String result = dm.resolveGame(Integer.parseInt(cmd[1]), cmd[2]);
			if (result.startsWith("valid"))
			{
				valid = true;
				result = "Game " + cmd[1] + " set to " + cmd[2];

				if (cmd[2].equals("void") && result.endsWith("display"))
					sendMsg(channel, "Game " + cmd[1] + " canceled (by admin)");
			}

			if (target.equalsIgnoreCase(botname))
				target = name;

			sendMsg(target, result);
		}

		// Log commands if necessary (except stats commands)
		if (valid && !text.startsWith(cmdchar + "stats"))
		{
			logAdmin("MOD (" + name + ") - " + text);
			sendMsg(achannel, "MOD (" + name + ") - " + text);
		}

		return valid;
	}
	/****************************\
	  End Admin Command Handlers
	\****************************/
}
